#include <windows.h>
#include <iostream>
#include <fstream>
#include "pugixml.hpp"

#include <vector>
#include <string>
#include <map>

std::vector<std::string> g_rpcclasses;

typedef std::vector<std::string> ParamatersList;

typedef std::map<std::string, ParamatersList> FunctionList;
typedef std::map<std::string, std::string> FunctionCallsAssoctable;
typedef std::map<std::string, std::string> FunctionProcsAssoctable;

bool IsObject(const std::string & objName)
{
	if (objName[0]=='s' && objName[1]=='t' && objName[2]=='d')
		return true;
	else 
		return false;
}

std::string g_currentInFilename;
std::string g_dir;

void Generate_ClientH_RpcStubFromXml()
{
	using namespace pugi;
	xml_document doc;

	FunctionList rpcfnlist;
	FunctionCallsAssoctable externelFnRpcTable;


	doc.load_file(g_currentInFilename.c_str());
	xml_node rpcGenNode = doc.child("RPCGEN");

	::SetCurrentDirectoryA((g_dir + rpcGenNode.attribute("o_dir").value()).c_str());

	std::fstream file_for_client(rpcGenNode.attribute("oh_file").value(), std::ios::out);

	file_for_client << "// don't edit!!!! Generated file." << std::endl << std::endl;
	file_for_client << "#ifndef _CLIENT_RPC_STUB_H_" << std::endl;
	file_for_client << "#define _CLIENT_RPC_STUB_H_" << std::endl;
 
	file_for_client << std::endl << "#include \"BinaryIO.h\"" << std::endl << std::endl;
	file_for_client << std::endl << "#include \"ZCRpc.h\"" << std::endl << std::endl;

	
	xml_node rpcCallNode = rpcGenNode.child("RPC");
	std::string rpcClass = rpcCallNode.attribute("class").value();

	for (xml_node fnNode = rpcCallNode.child("FN"); fnNode; fnNode = fnNode.next_sibling("FN"))
	{
		std::string fnName = fnNode.attribute("name").value();
		externelFnRpcTable[fnName] = fnNode.attribute("callfn").value();

		for (xml_node paramNode = fnNode.child("PARAM"); paramNode; paramNode = paramNode.next_sibling("PARAM"))
		{
			std::string paramType = paramNode.attribute("type").value();
			rpcfnlist[fnName].push_back(paramType);
		}
	}

	file_for_client << "#ifndef _RPC_CONSTS_H_" << std::endl;
	file_for_client << "#define _RPC_CONSTS_H_" << std::endl;
	file_for_client << "enum {" << std::endl;
	for (FunctionList::iterator it = rpcfnlist.begin();it != rpcfnlist.end();++it)
		file_for_client << it->first.c_str() << "," << std::endl;

	file_for_client << "RpcMaxFunctionNumber" << std::endl << "};" << std::endl << std::endl;
	file_for_client << "#endif" << std::endl;


	std::vector<std::string>::iterator itRpcClass;
	for (itRpcClass = g_rpcclasses.begin();itRpcClass!=g_rpcclasses.end();++itRpcClass)
	{
		file_for_client << "///////////////////////////////////////////////////////////////////////////////////////////////////////////////" << std::endl << std::endl;

		file_for_client << "class " << rpcClass << (*itRpcClass).c_str() << "RpcClient" << std::endl;
		file_for_client << "{" << std::endl;
		file_for_client << "public:" << std::endl;
		file_for_client << rpcClass << (*itRpcClass).c_str() << "RpcClient" << "(PVOID _target):m_target(_target){}" << std::endl;


		for (FunctionCallsAssoctable::iterator it = externelFnRpcTable.begin();it != externelFnRpcTable.end();++it)
		{
			ParamatersList & paramlist = rpcfnlist[it->first];
			file_for_client << "bool " << it->second << "(";
			
			unsigned int paramNumber = 0;
			for (ParamatersList::iterator it2 = paramlist.begin();it2 != paramlist.end();++it2)
			{
				if (it2!=paramlist.begin())
					file_for_client << ", ";
				file_for_client  << it2->c_str() << " & param" << ++paramNumber;
			}
			
			file_for_client << ");" <<  std::endl;
		}

		file_for_client << "private:" << std::endl;
		file_for_client << "PVOID m_target;" << std::endl;
		file_for_client << "};" << std::endl << std::endl << std::endl;

	}

	file_for_client << "#endif //_CLIENT_RPC_STUB_H_" << std::endl;
	file_for_client.close();	
}


void Generate_ClientCPP_RpcStubFromXml()
{
	using namespace pugi;
	xml_document doc;

	FunctionList rpcfnlist;
	FunctionCallsAssoctable externelFnRpcTable;


	doc.load_file(g_currentInFilename.c_str());
	xml_node rpcGenNode = doc.child("RPCGEN");

	::SetCurrentDirectoryA((g_dir + rpcGenNode.attribute("o_dir").value()).c_str());

	std::fstream file_for_client(rpcGenNode.attribute("ocpp_file").value(), std::ios::out);

	file_for_client << "// don't edit!!!! Generated file." << std::endl << std::endl;
 
	file_for_client << "#include <windows.h>" << std::endl;
	file_for_client << "#include \"BinaryIO.h\"" << std::endl << std::endl;

	file_for_client << "#include \"" << rpcGenNode.attribute("oh_file").value() << "\"" << std::endl << std::endl;


	xml_node rpcCallNode = rpcGenNode.child("RPC");
	std::string rpcClass = rpcCallNode.attribute("class").value();

	for (xml_node fnNode = rpcCallNode.child("FN"); fnNode; fnNode = fnNode.next_sibling("FN"))
	{
		std::string fnName = fnNode.attribute("name").value();
		externelFnRpcTable[fnName] = fnNode.attribute("callfn").value();

		for (xml_node paramNode = fnNode.child("PARAM"); paramNode; paramNode = paramNode.next_sibling("PARAM"))
		{
			std::string paramType = paramNode.attribute("type").value();
			rpcfnlist[fnName].push_back(paramType);
		}
	}

	std::vector<std::string>::iterator itRpcClass;
	for (itRpcClass = g_rpcclasses.begin();itRpcClass!=g_rpcclasses.end();++itRpcClass)
	{
		file_for_client << "///////////////////////////////////////////////////////////////////////////////////////////////////////////////" << std::endl << std::endl;

		for (FunctionCallsAssoctable::iterator it = externelFnRpcTable.begin();it != externelFnRpcTable.end();++it)
		{
			ParamatersList & paramlist = rpcfnlist[it->first];
			file_for_client << "bool " << rpcClass << (*itRpcClass).c_str() << "RpcClient" << "::" << it->second << "(";
			
			unsigned int paramNumber = 0;
			for (ParamatersList::iterator it2 = paramlist.begin();it2 != paramlist.end();++it2)
			{
				if (it2!=paramlist.begin())
					file_for_client << ", ";
				file_for_client  << it2->c_str() << " & param" << ++paramNumber;
			}
			
			file_for_client << ")" <<  std::endl << "{" << std::endl;

			paramNumber = 0;
			file_for_client << "ULONG bsize =";
			for (ParamatersList::iterator it3 = paramlist.begin();it3 != paramlist.end();++it3)
			{
				if (it3 != paramlist.begin())
					file_for_client << " +";

				file_for_client << " Commons::GetSize(";
				if (!IsObject(*it3))
					file_for_client << "&";
				file_for_client << "param";
				file_for_client << ++paramNumber;
				file_for_client << ")";
			}
			file_for_client << ";" << std::endl;


			file_for_client << "std::vector<char> data;" << std::endl;
			file_for_client << "data.resize(bsize);" << std::endl;
			file_for_client << "Commons::CBinaryWriter bw((PVOID)&data.front(), bsize);" << std::endl;

			paramNumber = 0;
			for (ParamatersList::iterator it4 = paramlist.begin();it4 != paramlist.end();++it4)
			{
				file_for_client << "bw.Write(";
				if (!IsObject(*it4))
					file_for_client << "&";
				file_for_client << "param";
				file_for_client << ++paramNumber;
				file_for_client << ");" << std::endl;
			}
			
			
			file_for_client << "bool rpcResult = " << rpcClass << (*itRpcClass).c_str() << "RpcCall" <<  "(m_target, " << it->first << ", data);" << std::endl;
			file_for_client << "if (!rpcResult) return false;" << std::endl;
			file_for_client << "Commons::CBinaryReader br((PVOID)&data.front(), data.size());" << std::endl;
			paramNumber = 0;
			for (ParamatersList::iterator it = paramlist.begin();it != paramlist.end();++it)
			{
				file_for_client << "br.Read(";
				if (!IsObject(*it))
					file_for_client << "&";
				file_for_client << "param" << ++paramNumber << ");" << std::endl;
			}
			file_for_client << "return rpcResult;" << std::endl;
			file_for_client << "}" << std::endl << std::endl;
		}

	}

	file_for_client.close();	
}





void Generate_ServerH_RpcStubFromXml()
{
	using namespace pugi;
	xml_document doc;

	FunctionList rpcfnlist;

	doc.load_file(g_currentInFilename.c_str());
	xml_node rpcGenNode = doc.child("RPCGEN");

	::SetCurrentDirectoryA((g_dir + rpcGenNode.attribute("o_dir").value()).c_str());

	std::fstream file_for_server(rpcGenNode.attribute("ih_file").value(), std::ios::out);

	file_for_server << "// don't edit!!!! Generated file." << std::endl << std::endl;
	file_for_server << "#ifndef _SERVER_RPC_STUB_H_" << std::endl;
	file_for_server << "#define _SERVER_RPC_STUB_H_" << std::endl;

	file_for_server << std::endl << "#include \"BinaryIO.h\"" << std::endl << std::endl;
	file_for_server << std::endl << "#include \"ZCRpc.h\"" << std::endl << std::endl;

	xml_node rpcProcNode = rpcGenNode.child("RPC");
	std::string rpcClass = rpcProcNode.attribute("class").value();
	for (xml_node fnNode = rpcProcNode.child("FN"); fnNode; fnNode = fnNode.next_sibling("FN"))
	{
		std::string fnName = fnNode.attribute("name").value();

		for (xml_node paramNode = fnNode.child("PARAM"); paramNode; paramNode = paramNode.next_sibling("PARAM"))
		{
			std::string paramType = paramNode.attribute("type").value();
			rpcfnlist[fnName].push_back(paramType);
		}
	}

	file_for_server << "#ifndef _RPC_CONSTS_H_" << std::endl;
	file_for_server << "#define _RPC_CONSTS_H_" << std::endl;
	file_for_server << "enum {" << std::endl;
	for (FunctionList::iterator it = rpcfnlist.begin();it != rpcfnlist.end();++it)
		file_for_server << it->first.c_str() << "," << std::endl;

	file_for_server << "RpcMaxFunctionNumber" << std::endl << "};" << std::endl << std::endl;
	file_for_server << "#endif" << std::endl;


	std::vector<std::string>::iterator itRpcClass;
	for (itRpcClass = g_rpcclasses.begin();itRpcClass!=g_rpcclasses.end();++itRpcClass)
	{
		// CXCRpcServerProcStub
		file_for_server << "namespace " << rpcClass << (*itRpcClass).c_str() << "RpcServer" << std::endl;
		file_for_server << "{" << std::endl;
		file_for_server << "bool RpcProc(ULONG fnid, std::vector<char> & data);" << std::endl << std::endl;

		for (xml_node fnNode = rpcProcNode.child("FN"); fnNode; fnNode = fnNode.next_sibling("FN"))
		{
			std::string RpcFnName = fnNode.attribute("name").value();
			std::string ProcFnName = fnNode.attribute("procfn").value();
			file_for_server << "bool " << ProcFnName.c_str() << "(";

			ParamatersList & paramlist = rpcfnlist[RpcFnName];

			unsigned int paramNumber = 0;
			for (ParamatersList::iterator it2 = paramlist.begin();it2 != paramlist.end();++it2)
			{
				if (it2!=paramlist.begin())
					file_for_server << ", ";
				file_for_server  << it2->c_str() << " & param" << ++paramNumber;
			}
			file_for_server << ");" << std::endl;

		}


		file_for_server << "};" << std::endl << std::endl << std::endl;
	}



	file_for_server << "#endif //_SERVER_RPC_STUB_H_" << std::endl;
	file_for_server.close();	
}


void Generate_ServerCPP_RpcStubFromXml()
{
	using namespace pugi;
	xml_document doc;

	FunctionList rpcfnlist;

	doc.load_file(g_currentInFilename.c_str());
	xml_node rpcGenNode = doc.child("RPCGEN");

	::SetCurrentDirectoryA((g_dir + rpcGenNode.attribute("o_dir").value()).c_str());

	std::fstream file_for_server(rpcGenNode.attribute("icpp_file").value(), std::ios::out);

	file_for_server << "// don't edit!!!! Generated file." << std::endl << std::endl;

	file_for_server << "#include <windows.h>" << std::endl;
	file_for_server << "#include \"BinaryIO.h\"" << std::endl << std::endl;
	file_for_server << "#include \"" << rpcGenNode.attribute("ih_file").value() << "\"" << std::endl << std::endl;
	file_for_server << "#include \"" << rpcGenNode.attribute("add_server_include").value() << "\"" << std::endl << std::endl;


	xml_node rpcProcNode = rpcGenNode.child("RPC");
	std::string rpcClass = rpcProcNode.attribute("class").value();
	for (xml_node fnNode = rpcProcNode.child("FN"); fnNode; fnNode = fnNode.next_sibling("FN"))
	{
		std::string fnName = fnNode.attribute("name").value();

		for (xml_node paramNode = fnNode.child("PARAM"); paramNode; paramNode = paramNode.next_sibling("PARAM"))
		{
			std::string paramType = paramNode.attribute("type").value();
			rpcfnlist[fnName].push_back(paramType);
		}
	}




	std::vector<std::string>::iterator itRpcClass;
	for (itRpcClass = g_rpcclasses.begin();itRpcClass!=g_rpcclasses.end();++itRpcClass)
	{

		file_for_server <<  "bool " << rpcClass << (*itRpcClass).c_str() << "RpcServer" << "::RpcProc(ULONG fnid, std::vector<char> & data)" << std::endl;
		file_for_server << "{" << std::endl;

		if (rpcProcNode.child("FN"))
		{
			file_for_server << "switch(fnid)" << std::endl;
			file_for_server << "{" << std::endl;
			for (xml_node fnNode = rpcProcNode.child("FN"); fnNode; fnNode = fnNode.next_sibling("FN"))
			{
				std::string RpcFnName = fnNode.attribute("name").value();
				std::string ProcFnName = fnNode.attribute("procfn").value();
				file_for_server << "case " << RpcFnName.c_str() << ":" << std::endl;
				file_for_server << "{" << std::endl;

				ParamatersList & paramlist = rpcfnlist[RpcFnName];

				unsigned int paramNumber = 0;
				for (ParamatersList::iterator it = paramlist.begin();it != paramlist.end();++it)
					file_for_server  << it->c_str() << " param" << ++paramNumber << ";" << std::endl;

				file_for_server  << "Commons::CBinaryReader br(&data.front(), data.size());" << std::endl;
				paramNumber = 0;
				for (ParamatersList::iterator it = paramlist.begin();it != paramlist.end();++it)
				{
					file_for_server << "br.Read(";
					if (!IsObject(*it))
						file_for_server << "&";
					file_for_server << "param" << ++paramNumber << ");" << std::endl;
				}
				file_for_server << ProcFnName.c_str() << "(";
					
				paramNumber = 0;
				for (ParamatersList::iterator it = paramlist.begin();it != paramlist.end();++it)
				{
					if (it!=paramlist.begin())
						file_for_server  << ", ";

					file_for_server << "param" << ++paramNumber;
				}
				file_for_server  << "); " << std::endl;


				paramNumber = 0;
				file_for_server << "ULONG bsize =";
				for (ParamatersList::iterator it = paramlist.begin();it != paramlist.end();++it)
				{
					if (it != paramlist.begin())
						file_for_server << "+";

					file_for_server << " Commons::GetSize(";
					if (!IsObject(*it))
						file_for_server << "&";
					file_for_server << "param";
					file_for_server << ++paramNumber;
					file_for_server << ")";
				}
				file_for_server << ";" << std::endl;
				file_for_server << "data.resize(bsize);" << std::endl;
				file_for_server << "Commons::CBinaryWriter bw((PVOID)&data.front(), bsize);" << std::endl;

				paramNumber = 0;
				for (ParamatersList::iterator it = paramlist.begin();it != paramlist.end();++it)
				{
					file_for_server << "bw.Write(";
					if (!IsObject(*it))
						file_for_server << "&";
					file_for_server << "param";
					file_for_server << ++paramNumber;
					file_for_server << ");" << std::endl;
				}
				file_for_server  << "return true;" << std::endl;
				file_for_server << "}" << std::endl << std::endl;
			}
			file_for_server << "default:" << std::endl;
			file_for_server << "return false;" << std::endl;
			file_for_server << "}" << std::endl;
		}
		file_for_server << "}" << std::endl << std::endl;
	}


	file_for_server.close();	
}

int main(int argc, char* argv[])
{
	g_rpcclasses.push_back("Pipe");
	g_rpcclasses.push_back("Network");

	if (argc!=2)
	{
		std::cout << "Usage: RpcGen.exe <Path to XML's>";
		return -1;
	}

	::SetCurrentDirectoryA(argv[1]);

	WIN32_FIND_DATAA findFileData;
	memset(&findFileData, 0, sizeof(findFileData));

	HANDLE hFindHandle = ::FindFirstFileA("*.xml", &findFileData);
	if (hFindHandle==INVALID_HANDLE_VALUE)
	{
		std::cout << "Error: Invalid directory";
		return -1;
	}

	do
	{
		std::cout << "Generate RPC Code for " << argv[1] << findFileData.cFileName << std::endl;
		
		::SetCurrentDirectoryA(argv[1]);
		g_currentInFilename = argv[1];
		g_currentInFilename += findFileData.cFileName;
		g_dir = argv[1];

		Generate_ClientH_RpcStubFromXml();
		Generate_ServerH_RpcStubFromXml();
		Generate_ClientCPP_RpcStubFromXml();
		Generate_ServerCPP_RpcStubFromXml();

	}while(::FindNextFileA(hFindHandle, &findFileData) != 0);

	::CloseHandle(hFindHandle);

	return 0;
}